﻿// Author: Hristo Hristov
// Date: 30.07.11

// Dependencies: 3D Model Prep from JCBDigger
// (http://code.google.com/p/3d-model-prep/)

// Revision 4

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNAnimation;
using XNAnimation.Controllers;
using XNAnimation.Effects;
using NOVA.Scenery;
using BEPUphysics.Entities;
using NOVA.Utilities;


namespace NOVA.Scenery.ComplexObjects
{
    public class RiggedModel : SceneObject//ModelObject//ITransformable
    {
        #region Members
        //Vector3 m_position;
        //Quaternion m_rotation;
        //Vector3 m_scale;
        //BoundingBox m_boundingBox;
        //Matrix m_controllerMatrix;
        //bool m_bHasController;

        Vector3[] vertices;
        int[] indices;

        BEPUphysics.MathExtensions.AffineTransform transform;
        float m_mass;

        BoundingSphere m_sphere;

        bool m_bShowBoundingSphere;

        private Matrix[] absoluteBoneTransforms;

        Matrix[] m_transforms;

        long m_prevTicks;

        /// <summary>
        /// The SkinnedModel class handles skeletal animated models
        /// </summary>
        XNAnimation.SkinnedModel skinnedModel;

        /// <summary>
        /// The AnimationController class handles the animation playback
        /// </summary>
        AnimationController animationController;
        
        #endregion

        #region Properties

        /// <summary>
        /// Gets the animation controller of the model, which can be used 
        /// to adjust the animation settings like animation speed, playback mode and so on.
        /// </summary>
        public AnimationController AnimationController
        {
            get { return animationController; }
        }

        /// <summary>
        /// If enabled, the collision mesh bounding sphere is shown.
        /// </summary>
        public bool ShowBoundingSphere
        {
            get { return m_bShowBoundingSphere; }
            set { m_bShowBoundingSphere = value; }
        }

        //public Microsoft.Xna.Framework.Vector3 Position
        //{
        //    get
        //    {
        //        return m_position;
        //    }
        //    set
        //    {
        //        m_position = value;
        //    }
        //}

        //public Microsoft.Xna.Framework.Quaternion Orientation
        //{
        //    get
        //    {
        //        return m_rotation;
        //    }
        //    set
        //    {
        //        m_rotation = value;
        //    }
        //}

        //public Microsoft.Xna.Framework.Vector3 Scale
        //{
        //    get
        //    {
        //        return m_scale;
        //    }
        //    set
        //    {
        //        m_scale = value;
        //    }
        //}

        //public Microsoft.Xna.Framework.BoundingBox BoundingBox
        //{
        //    get { return m_boundingBox; }
        //}

        //public Microsoft.Xna.Framework.Matrix ControllerMatrix
        //{
        //    get
        //    {
        //        return m_controllerMatrix;
        //    }
        //    set
        //    {
        //        m_controllerMatrix = value;
        //    }
        //}

        //public bool HasController
        //{
        //    get
        //    {
        //        return m_bHasController;
        //    }
        //    set
        //    {
        //        m_bHasController = value;
        //    }
        //}

        public override BoundingBox BoundingBox
        {
            get
            {
                return new BoundingBox(base.Position - Vector3.One, base.Position + Vector3.One);
            }
        }

        #endregion

        #region Constructor

        public RiggedModel(Vector3 pos, string strAsset, string strPhysAsset, float mass) :base()//: base(pos, strPath, strAssetName, mass)
        {
            // Load the skinned model
            skinnedModel = Core.Content.Load<XNAnimation.SkinnedModel>(strAsset);
            absoluteBoneTransforms = new Matrix[skinnedModel.SkeletonBones.Count];

            // Create an animation controller and start a clip
            animationController = new AnimationController(skinnedModel.SkeletonBones);

            m_mass = mass;
            
            // Load the model as normal model for BEPU physics by loading the mesh data and creating the group.            
            Model modelXNA = Core.Content.Load<Model>(strPhysAsset);

            BoundingSphere sphere = modelXNA.Meshes[0].BoundingSphere;
            BEPUphysics.CollisionShapes.ConvexShapes.SphereShape shape = new BEPUphysics.CollisionShapes.ConvexShapes.SphereShape(sphere.Radius);
            m_graphicsPhysOffset = sphere.Center;
            Physics = new MorphableEntity(shape);

            m_sphere = sphere;

            m_bShowBoundingSphere = false;
            
            m_prevTicks = DateTime.Now.Ticks;
        }

        #endregion

        /// <summary>
        /// Starts the animation of the given clip.
        /// </summary>
        /// <param name="clipName">Name of the animation clip in the model, which should be started.</param>
        public void StartClip(string clipName)
        {
            animationController.StartClip(skinnedModel.AnimationClips[clipName]);            
        }

        /// <summary>
        /// Starts the animation of the given animation clip and performs a cross fading in the given time span.
        /// </summary>
        /// <param name="clipName">Name of the animation clip in the model, which should be started.</param>
        /// <param name="fadeTime">Time span for the duration of the cross fading.</param>
        public void CrossFadeTo(string clipName, TimeSpan fadeTime)
        {
            animationController.CrossFade(skinnedModel.AnimationClips[clipName], fadeTime);
        }
        
        /// <summary>
        /// Updates the animation.
        /// </summary>
        new public void Update(GameTime gameTime)
        {
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);
        }
        
        /// <summary>
        /// Draws the model.
        /// </summary>
        public override void Draw(Matrix world, bool bEnableLight, CameraObject camera)
        {
            if (!Visible)
                return;

            RasterizerState oldRasterizer = Core.Device.RasterizerState;
            RasterizerState newRasterizer = new RasterizerState() { CullMode = CullMode.CullCounterClockwiseFace,  };
            Core.Device.RasterizerState = newRasterizer;

            // THIS IS IMPORTANT:
            // Adapt the graphical mesh to the collision mesh using the intial offset
            Matrix newWorld = Matrix.CreateTranslation(-m_graphicsPhysOffset) * world;

            if (m_bShowBoundingSphere)
            {
                Vector3 center = Vector3.Transform(m_sphere.Center, newWorld);
                BoundingSphere sphere = new Microsoft.Xna.Framework.BoundingSphere(center, m_sphere.Radius);
                DebugShapeRenderer.AddBoundingSphere(sphere, Color.Red);
            }


            Matrix tmpMat1;                       

            long etTicks = DateTime.Now.Ticks;
            long scaledTicks = (long)((etTicks - m_prevTicks) * 1f);
            TimeSpan adjustedElapsedTime = new TimeSpan(scaledTicks);
            animationController.Update(adjustedElapsedTime, Matrix.Identity);
            m_prevTicks = etTicks;
            
            // Enable the depth buffer
            DepthStencilState oldState = Core.Device.DepthStencilState;
            DepthStencilState newState = new DepthStencilState();
            newState.DepthBufferEnable = true;
            Core.Device.DepthStencilState = newState;

            BlendState oldBlend = Core.Device.BlendState;
            if (base.RenderMaterial.Transparency == 0)
                Core.Device.BlendState = BlendState.Opaque;
            else
                Core.Device.BlendState = BlendState.AlphaBlend;

            // Render the actual model
            foreach (SkinnedModelMesh modelMesh in skinnedModel.Meshes)
            {
                SkinnedModelBasicEffect basicEffect = modelMesh.Effect;

                basicEffect.World = newWorld;
                basicEffect.Bones = animationController.SkinnedBoneTransforms;
                basicEffect.View = Core.ViewMatrix;
                basicEffect.Projection = Core.ProjectionMatrix;

                // Configure material
                //basicEffect.Material.DiffuseColor = new Vector3(base.RenderMaterial.Diffuse.X, base.RenderMaterial.Diffuse.Y, base.RenderMaterial.Diffuse.Z);
                //basicEffect.Material.SpecularColor = new Vector3(base.RenderMaterial.Specular.X, base.RenderMaterial.Specular.Y, base.RenderMaterial.Specular.Z);
                //basicEffect.Material.SpecularPower = base.RenderMaterial.SpecularPower;

                if (base.RenderMaterial.Texture != null)
                {
                    basicEffect.DiffuseMapEnabled = true;
                    basicEffect.DiffuseMap = base.RenderMaterial.Texture;
                }
                else
                    basicEffect.DiffuseMapEnabled = false;

                if (base.RenderMaterial.NormalMap != null)
                {
                    basicEffect.NormalMapEnabled = true;
                    basicEffect.NormalMap = base.RenderMaterial.NormalMap;
                }
                else
                    basicEffect.NormalMapEnabled = false;

                if (base.RenderMaterial.SpecularMap != null)
                {
                    basicEffect.SpecularMapEnabled = true;
                    basicEffect.SpecularMap = base.RenderMaterial.SpecularMap;
                }
                else
                    basicEffect.SpecularMapEnabled = false;

                // OPTIONAL - Configure lights
                basicEffect.AmbientLightColor = new Vector3(10.1f);
                basicEffect.LightEnabled = true;
                basicEffect.EnabledLights = EnabledLights.Four;
                basicEffect.PointLights[0].Color = Vector3.One;
                basicEffect.PointLights[0].Position = new Vector3(10, 10, 10);
                basicEffect.PointLights[1].Color = Vector3.One;
                basicEffect.PointLights[1].Position = new Vector3(-10, 10, 10);
                basicEffect.PointLights[2].Color = Vector3.One;
                basicEffect.PointLights[2].Position = new Vector3(10, 10, -10);
                basicEffect.PointLights[3].Color = Vector3.One;
                basicEffect.PointLights[3].Position = new Vector3(-10, 10, -10);

                modelMesh.Draw();
            }

            Core.Device.DepthStencilState = oldState;

            Core.Device.RasterizerState = oldRasterizer;

            Core.Device.BlendState = oldBlend;
        }
    }
}
